/**
 * 
 */
package org.stars.daostars.sqlmapper;

import java.io.Serializable;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.stars.daostars.conversion.TypeAlias;
import org.stars.daostars.core.DaoMap;
import org.stars.daostars.core.SqlParameter;
import org.stars.daostars.core.SqlQuery;
import org.stars.daostars.core.SqlQueryReference;
import org.stars.daostars.core.conditions.Condition;
import org.stars.daostars.pipeline.DaoPipeline;
import org.stars.datastructure.map.CaseInsensitiveHashMap;
import org.stars.util.StringUtility;

/**
 * Definisce le query sql, le mappe, i relativi attributi associati ad un Dao.
 * Per ogni dao si definiscono le seguenti entita':
 * <ul>
 * <li><b>name</b>: nome della query</li>
 * <li><b>query(s)</b>: comprende sia la definizione della query, sia i
 * parametri</li>
 * <li><b>query condition(s)</b></li>
 * <li><b>query base</b>: è la query da cui deriva la query definita.</li>
 * <li><b>map(s)</b></li>
 * </ul>
 * 
 * @author Francesco Benincasa
 * 
 */
public class SqlMapper implements Serializable {
	
	protected Log getLog()
	{
		return LogFactory.getLog(getClass());
	}
	
	/**
	 * Insieme degli errori che si sono generati durante  il parsing
	 */
	protected List<SqlMapperError> parserErrors;
	
	public void addParserError(String msg, Class<?> type )
	{
		parserErrors.add(new SqlMapperError(msg, type));
	}
	
	public void addParserError(Exception e )
	{
		parserErrors.add(new SqlMapperError(e));
	}
	
	
	public void addParserError(String msg)
	{
		parserErrors.add(new SqlMapperError(msg));
	}
	
	public List<SqlMapperError> getParserErrors() {
		return parserErrors;
	}

	public void setParserErrors(List<SqlMapperError> parserErrors) {
		this.parserErrors = parserErrors;
	}

	protected Map<String, TypeAlias> typeAliasSet;
	
	/**
	 * @return the typeAliasSet
	 */
	public Map<String, TypeAlias> getTypeAliasSet() {
		return typeAliasSet;
	}

	/**
	 * @param typeAliasSet the typeAliasSet to set
	 */
	public void setTypeAliasSet(Map<String, TypeAlias> typeAliasSet) {
		this.typeAliasSet = typeAliasSet;
	}

	/**
	 * set delle query definite in altri dao ma utilizzati qua.
	 */
	protected Map<String, SqlQueryReference> queryReferenceSet;
	
	/**
	 * serial id
	 */
	protected static final long serialVersionUID = 4360181907271970209L;

	/**
	 * nome della connessione di default
	 */
	protected String defaultConnectionName;
	
	/**
	 * insieme delle condizioni definite nel dao
	 */
	protected Map<String, Condition> conditionSet;
	
	/**
	 * insieme delle condizioni definite nel dao
	 */
	protected Map<String, DaoPipeline> pipelineSet;

	/**
	 * Hashbtable contenente le mappe associate al dao.
	 */
	protected Map<String, DaoMap> mapSet;
	
	/**
	 * Hashbtable contenente le proprietà associate al dao.
	 */
	protected Map<String, String> variableSet;
	
	/**
	 * Nome della definizione del dao.
	 */
	protected String name;

	/**
	 * Hashtable contenente per tutti i dao tutte le definizioni delle query. La
	 * chiave è il nome del codice sql definito mediante l'attributo
	 * <code>name</code> del tag <code>query</code>.
	 */
	protected Map<String, SqlQuery> querySet;

	/**
	 * Costruttore di default. Inizializza anche il
	 * connection pool di default ad un valore nullo
	 */
	public SqlMapper() {
		this("","");
	}

	/**
	 * Restituisce <code>true</code> se è definita la condizione il cui nome è
	 * passato come parametro.
	 * 
	 * @param value
	 *            nome della condizione da verificare
	 * @return <code>true</code> se la condizione esiste, <code>false</code>
	 *         altrimenti
	 */
	public boolean existsCondition(String value) {
		boolean bRet = false;
		value = value.toUpperCase();
		if (conditionSet.containsKey(value)) {
			bRet = true;
		}

		return bRet;
	}
	
	/**
	 * Restituisce <code>true</code> se è definita la query il cui nome è
	 * passato come parametro.
	 * 
	 * @param value
	 *            nome della querye da verificare
	 * @return <code>true</code> se la query esiste, <code>false</code>
	 *         altrimenti
	 */
	public boolean existsQuery(String value) {
		boolean bRet = false;
		value = value.toUpperCase();
		if (querySet.containsKey(value)) {
			bRet = true;
		}

		return bRet;
	}
	
	public boolean existsIncludedQuery(String value)
	{
		boolean bRet = false;
		value = value.toUpperCase();
		if (queryReferenceSet.containsKey(value)) {
			bRet = true;
		}

		return bRet;
	}
	
	/**
	 * Recupera una queryReference in base al suo nome. Nel
	 * caso non esista, viene restituito <code>null</code>.
	 * 
	 * @param value
	 * @return
	 * 		queryReference
	 */
	public SqlQueryReference getIncludedQuery(String value)
	{
		value = value.toUpperCase();
		return queryReferenceSet.get(value);
	}
	
	/**
	 * Costruttore di default
	 * 
	 * @param sName
	 * 		nome del Dao
	 * @param connectionName 
	 * 		nome della connessione da utilizzare
	 */
	public SqlMapper(String sName, String connectionName) {
		name = StringUtility.nvl(sName);

		// creiamo gli attributi
		mapSet = new CaseInsensitiveHashMap<DaoMap>();
		defaultConnectionName=connectionName;
		
		// aggiungiamo la mappa mapAllFields, che mappa tutti i campi
		DaoMap mapAllFields=new DaoMap(DaoMap.MAP_ALL_ATTRIBUTES);
		
		mapAllFields.addPropertyMapping("*", "*");
		setMap(mapAllFields.getName(), mapAllFields);
		
		querySet 			= new CaseInsensitiveHashMap<SqlQuery>();
		conditionSet 		= new CaseInsensitiveHashMap<Condition>();
		variableSet			= new CaseInsensitiveHashMap<String>();
		pipelineSet			= new CaseInsensitiveHashMap<DaoPipeline>();
		queryReferenceSet	= new CaseInsensitiveHashMap<SqlQueryReference>();		
		typeAliasSet		= new CaseInsensitiveHashMap<TypeAlias>();
		
		parserErrors		=	new LinkedList<SqlMapperError>();
		
	}

	public Condition getCondition(String key) {
		key = key.toUpperCase();
		if (conditionSet.containsKey(key)) {
			return conditionSet.get(key);
		}
		return null;
	}

	/**
	 * Getter dell'attributo conditionSet.
	 * 
	 * @return the conditionSet
	 */
	public Map<String, Condition> getConditionSet() {
		return conditionSet;
	}

	/**
	 * Recupera una mapp.
	 * 
	 * @param value
	 * 		nome della mappa
	 * @return
	 * 		mappa di conversione o <code>null</code> nel caso essa
	 * 		non esista 
	 */
	public DaoMap getMap(String value) {
		value = value.toUpperCase();

		if (mapSet.containsKey(value)) {
			return mapSet.get(value);
		}
		return null;
	}
		

	/**
	 * Getter dell'attributo mapSet.
	 * 
	 * @return the mapSet
	 */
	public Map<String, DaoMap> getMapSet() {
		return mapSet;
	}

	/**
	 * Getter dell'attributo name.
	 * 
	 * @return the name
	 */
	public String getName() {
		return name;
	}

	/**
	 * Recupera una query.
	 * 
	 * @param value
	 * @return
	 * 		definizione della query 
	 */
	public SqlQuery getQuery(String value) {
		value = value.toUpperCase();

		if (querySet.containsKey(value)) {
			return querySet.get(value);
		}
		return null;
	}
	
	/**
	 * Recupera un alias
	 * @param key
	 * @return
	 */
	public TypeAlias getTypeAlias(String key)
	{
		key=key.toUpperCase();
		
		if (typeAliasSet.containsKey(key))
		{
			return typeAliasSet.get(key);
		}
		
		return null;
	}
	
	/**
	 * Recupera la definizionedi query in base al suo nome. Nel caso non esiste
	 * viene restituito <code>null</code>.
	 * 
	 * @param value
	 * 
	 * @return
	 * 		<code>ArrayList</code> dei parametri della query
	 */
	public List<SqlParameter> getQueryParameters(String value) {
		value = value.toUpperCase();

		if (querySet.containsKey(value)) {
			SqlQuery app = querySet.get(value);
			return app.getParameters();
		} else {
			return null;
		}
	}

	/**
	 * Getter dell'attributo querySet.
	 * 
	 * @return the querySet
	 */
	public Map<String, SqlQuery> getQuerySet() {
		return querySet;
	}
	
	/**
	 * Indica se una query è definita o no per questa definizione
	 * 
	 * @param queryName
	 * 			nome della query
	 * @return
	 * 		<code>true</code> se esiste una query con il nome passato
	 * 		come parametro
	 */
	public boolean isDefined(String queryName) {
		queryName=queryName.toUpperCase();
		
		if (querySet.containsKey(queryName)) {
			return true;
		}
		return false;
	}

	/**
	 * Recupera la definizione della query con i parametri risolti e senza
	 * alcuna clausola condizionale.
	 * 
	 * @param value
	 * 			nome della query
	 * @return
	 * 			codice sql della query
	 */
	public String getQuerySql(String value) {
		SqlQuery query = getQuery(value);

		if (query == null)
			return "";

		return StringUtility.nvl(query.getSql(), "");
	}

	public void setConditionalClause(String name, Condition value) {
		name = name.toUpperCase();
		value.setName(name);
		conditionSet.put(name, value);
	}

	/**
	 * Setter dell'attributo conditionSet.
	 * 
	 * @param conditionSet
	 *            the conditionSet to set
	 */
	public void setConditionSet(
			Map<String, Condition> conditionSet) {
		this.conditionSet = conditionSet;
	}

	/**
	 * Imposta una mappa per il dao.
	 * 
	 * @param name
	 * @param value
	 */
	public void setMap(String name, DaoMap value) {
		name = name.toUpperCase();
		value.setName(name);

		mapSet.put(name, value);
	}

	/**
	 * Setter dell'attributo mapSet.
	 * 
	 * @param mapSet
	 *            the mapSet to set
	 */
	public void setMapSet(Map<String, DaoMap> mapSet) {
		this.mapSet = mapSet;
	}

	/**
	 * Setter dell'attributo name.
	 * 
	 * @param value
	 *            the name to set
	 */
	public void setName(String value) {
		this.name = value;
	}

	/**
	 * Imposta una query per il dao. Nel caso di duplice inserimento,
	 * viene segnalato come errore.
	 * 
	 * @param name
	 * 		nome della query
	 * @param value
	 * 		query
	 */
	public void setQuery(String name, SqlQuery value) {
		name = name.toUpperCase();
		value.setName(name);

		if (querySet.containsKey(name))
		{
			Log logger=getLog();
			
			logger.error("Query "+name+" in dao "+this.name+" already defined. It will be replace.");
		}
		querySet.put(name, value);
	}	
	
	/**
	 * Imposta una pipeline
	 * 
	 * @param name
	 * @param value
	 */
	public void setPipeline(String name, DaoPipeline value) {
		name = name.toUpperCase();
		value.setName(name);

		pipelineSet.put(name, value);
	}	
	
	/**
	 * @param key
	 * @param value
	 */
	public void setTypeAlias(String key, TypeAlias value)
	{
		key=key.toUpperCase();
		
		value.setName(key);
		
		typeAliasSet.put(key, value);
	}
	
	
	/**
	 * Imposta una query reference
	 * 
	 * @param name
	 * 		nome della query
	 * @param value
	 * 		reference
	 */
	public void setQueryReference(String name, SqlQueryReference value) {
		name=name.toUpperCase();
		value.setQueryName(name);
		
		queryReferenceSet.put(name, value);
	}
	/**
	 * Imposta una pipeline
	 * 
	 * @param value
	 * 		nome della pipeline da recuperare
	 * @return
	 * 		pipeline o null (nel caso non esista)
	 */
	public DaoPipeline getPipeline(String value) {
		value = value.toUpperCase();
		
		return pipelineSet.get(value);
	}	

	/**
	 * Setter dell'attributo querySet.
	 * @param value 
	 *            the querySet to set
	 */
	public void setQuerySet(Map<String, SqlQuery> value) {
		this.querySet = value;
	}

	/**
	 * Getter dell'attributo defaultConnectionName.
	 * @return
	 *		the defaultConnectionName
	 */
	public String getDefaultConnectionName() {
		return defaultConnectionName;
	}

	/**
	 * Setter dell'attributo defaultConnectionName.
	 * @param defaultConnectionName
	 *		the defaultConnectionName to set
	 */
	public void setDefaultConnectionName(String defaultConnectionName) {
		this.defaultConnectionName = defaultConnectionName;
	}

	/**
	 * @return the propertySet
	 */
	public Map<String, String> getVariableSet() {
		return variableSet;
	}

	/**
	 * @param propertySet the propertySet to set
	 */
	public void setVariableSet(Map<String, String> propertySet) {
		this.variableSet = propertySet;
	}
	
	/**
	 * Inserisce una variabile. Nel caso in cui <code>key</code> o <code>value</code>
	 * siano posti a <code>null</code>, l'inserimento non viene
	 * effettuato.
	 * 
	 * @param key
	 * 			nome della variabile
	 * @param value
	 * 			valore della variabile
	 */
	public void setVariable(String key, String value)
	{
		key=StringUtility.nvl(key).toUpperCase();
		
		if (value!=null && key.length()>0)
			this.variableSet.put(key.toUpperCase(), value);
	}
	
	/**
	 * Verifica che la definizione contenga una variabile il
	 * cui nome viene passato come parametro del metodo.
	 * 
	 * @param key
	 * 			nome della chiave
	 * @return
	 * 			<code>true</code> se esiste la varibile, <code>false</code> altrimenti.
	 */
	public boolean containsVariable(String key)
	{
		key=StringUtility.nvl(key).toUpperCase();
		
		if (variableSet.containsKey(key)) return true;
		
		return false;			
	}
		
	/**
	 * Restituisce il valore di una variabile. Se la variabile non
	 * è definita, viene restituito <code>null</code>. Se la variabile
	 * è definita ma non è specificato l'attributo <code>value</code> viene
	 * restituita una stringa vuota.
	 * 
	 * @param key
	 * 		nome della variabile
	 * @return
	 * 		valore della variabile o <code>null</code> nel caso essa non esiste
	 */
	public String getVariable(String key)
	{
		key=StringUtility.nvl(key).toUpperCase();
		
		if (variableSet.containsKey(key))
		{
			return (String)variableSet.get(key);
		}
		return null;
	}

	/**
	 * Getter dell'attributo pipelineSet
	 * @return the pipelineSet
	 */
	public Map<String, DaoPipeline> getPipelineSet() {
		return pipelineSet;
	}

	/**
	 * Setter dell'attributo pipelineSet
	 * @param pipelineSet the pipelineSet to set
	 */
	public void setPipelineSet(Map<String, DaoPipeline> pipelineSet) {
		this.pipelineSet = pipelineSet;
	}

	/**
	 * Getter dell'attributo queryReferenceSet
	 * @return the queryReferenceSet
	 */
	public Map<String, SqlQueryReference> getQueryReferenceSet() {
		return queryReferenceSet;
	}

	/**
	 * Setter dell'attributo queryReferenceSet
	 * @param queryReferenceSet the queryReferenceSet to set
	 */
	public void setQueryReferenceSet(Map<String, SqlQueryReference> queryReferenceSet) {
		this.queryReferenceSet = queryReferenceSet;
	}

	/**
	 * return a copy of variables set.
	 * @return
	 */
	public Map<String, String> getVariableSetCopy() {
		Map<String, String> copy=new HashMap<String, String>();
		copy.putAll(variableSet);
		
		return copy;
	}



}
